fix(i18n): locale fallback, auto-detection, and service-i18n ESM export fix#914
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ed warnings - Add resolveLocale() helper to core fallbacks for locale code fallback (zh → zh-CN, en-us → en-US, etc.) - Enhance createMemoryI18n() to use locale fallback in getTranslations() and t() - DevPlugin: replace duplicate createI18nStub with createMemoryI18n from core; auto-detect stack translations and try I18nServicePlugin before stub - AppPlugin: emit clear warnings when translations exist but no i18n service or only a fallback/stub is registered - HttpDispatcher: add locale fallback in handleI18n() for translations and labels - CLI serve: auto-detect translations in stack config and register I18nServicePlugin Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
- Fix service-i18n package.json exports (index.mjs → index.js for ESM) resolving "Cannot find module" error when importing @objectstack/service-i18n - Add @objectstack/service-i18n as optional peer dep in plugin-dev - Add locale fallback tests for resolveLocale() and createMemoryI18n - Add locale fallback tests for HttpDispatcher.handleI18n() - Update app-plugin tests for enhanced i18n warning behavior - Fix TypeScript cast in app-plugin (II18nService → unknown → Record) - Update CHANGELOG.md with i18n plugin registration mechanism changes Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
@copilot 仔细评估是否已经完成了所有开发要求并解决以下问题18:29:15.763 @objectstack/spec:build: ✓ data/TenantDatabaseLifecycle.json |
…onal @objectstack/service-i18n
The CLI package uses tsc (not tsup) for building, so a static string
literal in `import('@objectstack/service-i18n')` causes TS2307 because
the package isn't in CLI's dependencies. Using a variable indirection
prevents tsc from attempting module resolution at compile time.
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Fixed in |
There was a problem hiding this comment.
Pull request overview
This PR improves i18n ergonomics across ObjectStack by introducing locale code fallback (e.g., zh → zh-CN), standardizing the in-memory i18n fallback behavior, and auto-registering the production i18n plugin when translation config is detected.
Changes:
- Add
resolveLocale()and integrate locale fallback intocreateMemoryI18n, plus extend core fallback tests. - Enhance runtime i18n REST handling (translations + labels) and add translation fallback tests.
- Auto-register
I18nServicePluginin DevPlugin and CLIservewhen translations/i18n config is present; improve AppPlugin diagnostics around missing i18n service.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds workspace link for @objectstack/service-i18n. |
| packages/services/service-i18n/package.json | Updates export map to import: dist/index.js and require: dist/index.cjs. |
| packages/runtime/src/http-dispatcher.ts | Adds locale resolution logic to i18n translations/labels routes. |
| packages/runtime/src/http-dispatcher.test.ts | Adds tests for translation locale fallback behavior. |
| packages/runtime/src/app-plugin.ts | Improves logging/diagnostics when translations exist but i18n service is missing or fallback/dev. |
| packages/runtime/src/app-plugin.test.ts | Updates expectations to match new warning behavior. |
| packages/plugins/plugin-dev/src/dev-plugin.ts | Uses core createMemoryI18n for dev stub + auto-registers I18nServicePlugin when translations detected. |
| packages/plugins/plugin-dev/package.json | Adds optional peer/dev dependency on @objectstack/service-i18n. |
| packages/core/src/fallbacks/memory-i18n.ts | Introduces resolveLocale() and makes memory i18n resolve locales for t()/getTranslations(). |
| packages/core/src/fallbacks/index.ts | Re-exports resolveLocale from the fallbacks barrel. |
| packages/core/src/fallbacks/fallbacks.test.ts | Adds unit tests for resolveLocale and memory-i18n locale fallback. |
| packages/cli/src/commands/serve.ts | Auto-registers I18nServicePlugin when config indicates i18n/translations. |
| CHANGELOG.md | Documents the new locale fallback + plugin auto-detection behavior (needs cleanup/accuracy fixes). |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
| let translations = i18nService.getTranslations(locale); | ||
|
|
||
| // Locale fallback: try resolving to an available locale when | ||
| // the exact code yields empty translations (e.g. zh → zh-CN). | ||
| if (Object.keys(translations).length === 0) { |
There was a problem hiding this comment.
The translations endpoint only attempts locale resolution when getTranslations(locale) returns an empty object. This breaks with createMemoryI18n, which now resolves locale internally (e.g. requesting zh can return zh-CN translations without being empty), causing the response to report locale: "zh" even though the payload is for zh-CN and requestedLocale is omitted. Consider resolving the locale up-front (using getLocales() + resolveLocale) and then calling getTranslations(resolved) so the response locale always matches the actual translation bundle (and include requestedLocale when it differs).
| const hasI18nPlugin = plugins.some( | ||
| (p: any) => p.name === 'com.objectstack.service.i18n' | ||
| || p.constructor?.name === 'I18nServicePlugin' | ||
| ); |
There was a problem hiding this comment.
hasI18nPlugin only detects instantiated plugin objects. serve.ts also supports plugins declared as strings (package names) later in the same command, so a config that includes '@objectstack/service-i18n' as a string can lead to the i18n plugin being auto-registered here and then registered again when iterating over plugins. Update the detection to also handle string entries (e.g. exact match / substring), or check kernel service/plugin registration state before auto-registering.
| const hasI18nPlugin = plugins.some( | |
| (p: any) => p.name === 'com.objectstack.service.i18n' | |
| || p.constructor?.name === 'I18nServicePlugin' | |
| ); | |
| const hasI18nPlugin = Array.isArray(plugins) && plugins.some((p: any) => { | |
| if (!p) return false; | |
| // String plugin declaration, e.g. '@objectstack/service-i18n' | |
| if (typeof p === 'string') { | |
| const normalized = p.toLowerCase(); | |
| return ( | |
| normalized === '@objectstack/service-i18n' || | |
| normalized.includes('service-i18n') | |
| ); | |
| } | |
| // Instantiated plugin object | |
| return ( | |
| p.name === 'com.objectstack.service.i18n' || | |
| p.constructor?.name === 'I18nServicePlugin' | |
| ); | |
| }); |
| // Locale fallback for labels endpoint | ||
| const availableLocales = typeof i18nService.getLocales === 'function' | ||
| ? i18nService.getLocales() : []; | ||
| const resolved = resolveLocale(locale, availableLocales); | ||
| if (resolved) locale = resolved; |
There was a problem hiding this comment.
New locale fallback behavior was added for the labels endpoint, but the handleI18n test suite doesn’t currently cover a fallback case (e.g. /labels/account/zh resolving to zh-CN, or case-insensitive matching). Adding a focused test will prevent regressions and confirm the resolved locale is reflected in the response.
| (`zh` → `zh-CN`). Used by `createMemoryI18n`, `HttpDispatcher.handleI18n()`, and | ||
| `I18nServicePlugin` route handlers. |
There was a problem hiding this comment.
This changelog entry claims resolveLocale() is used by I18nServicePlugin route handlers, but the current I18nServicePlugin.registerI18nRoutes() implementation returns translations for the requested locale without any fallback. Either update the plugin routes to use resolveLocale (and document behavior), or adjust this changelog text so it matches the actual implementation.
| (`zh` → `zh-CN`). Used by `createMemoryI18n`, `HttpDispatcher.handleI18n()`, and | |
| `I18nServicePlugin` route handlers. | |
| (`zh` → `zh-CN`). Used by `createMemoryI18n` and `HttpDispatcher.handleI18n()`. |
| - CLI `serve` command now auto-registers `I18nServicePlugin` when config has translations/i18n, | ||
| mirroring DevPlugin's auto-detection behavior for production environments. | ||
|
|
||
| ### Changed |
There was a problem hiding this comment.
There are two consecutive ### Changed sections in the Unreleased block, which makes the changelog harder to read and can confuse release tooling/readers. Consider merging these into a single ### Changed section (or renaming one of them if it’s meant to be a different category).
| ### Changed |
resolveLocale()) topackages/core/src/fallbacks/memory-i18n.tsresolveLocale()helper from core fallbacks for reusecreateI18nStub()withcreateMemoryI18nfrom core; auto-detect stack translations and try I18nServicePluginhandleI18n()translations and labels routes@objectstack/service-i18nOriginal prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.